﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using LingDong.IndexEntities.Dictionary;
using LingDong.IndexEntities.Document;
using LingDong.SerializeProvider;
using NLog;
using PanGu;

namespace LingDong.IndexCreator
{
    internal class DictionaryIndex
    {
        #region Constructor

        internal DictionaryIndex()
        {
            termList = new Dictionary<string, DictionaryIndexEntity>();
            segment = new Segment();
        }

        #endregion

        
        #region Internal Members

        /// <summary>
        /// Add words appears in a parsed document to index
        /// </summary>
        /// <param name="document"></param>
        internal void AddDocument(ParsedDocumentEntity document)
        {
            // title
            AddSentence(document.DocID, document.Title, 0, TermHTMLPosition.Title);
            
            // content
            int sentenceNum = document.SentenceList.Length;
            for (short i = 0; i < sentenceNum; i++)
            {
                AddSentence(document.DocID, document.SentenceList[i], i, TermHTMLPosition.Normal);
            }
        }

        /// <summary>
        /// Before save current sub index to files, update dictionary info
        /// Including stop words info, DF, TF, TotalTF and termPOS
        /// </summary>
        internal void UpdateDictionaryInfo()
        {
            foreach (KeyValuePair<string, DictionaryIndexEntity> pair in termList)
            {
                string term = pair.Key;
                DictionaryIndexEntity indexEntity = pair.Value;

                UpdateDictionaryEntityInfo(term, indexEntity);
            }
        }

        /// <summary>
        /// After process enough documents, save the dictionary related indexs to files
        /// </summary>
        /// <param name="invertedListFname">inverted list file name, generally X.InvertedList</param>
        /// <param name="dictionaryFname">dictionary file name, generally X.Dictionary</param>
        internal void SaveToFile(string invertedListFname, string dictionaryFname)
        {
            try
            {
                // inverted list
                using (FileStream fs = new FileStream(invertedListFname, FileMode.Create))
                {
                    foreach (DictionaryIndexEntity indexEntity in termList.Values)
                    {
                        indexEntity.InvertedListOffset = fs.Position;
                        Serializer.Serialize<Dictionary<int, InvertedListEntity>>(fs, indexEntity.InvertedList);

                    }
                }

                // dictionary
                using (FileStream fs = new FileStream(dictionaryFname, FileMode.Create))
                {
                    Serializer.Serialize<Dictionary<string, DictionaryIndexEntity>>(fs, termList);
                }


                Dictionary<string, DictionaryIndexEntity> newTermList;
                using (FileStream fs = new FileStream(dictionaryFname, FileMode.Open))
                {
                    newTermList = Serializer.Deserialize<Dictionary<string, DictionaryIndexEntity>>(fs);
                }
            }
            catch (IOException ex)
            {
                Logger logger = LogManager.GetCurrentClassLogger();
                logger.Error("Error sub index creator, dictionary index,save to files, IOException: {0}", ex.Message);
                throw;
            }
            catch (Exception ex)
            {
                Logger logger = LogManager.GetCurrentClassLogger();
                logger.Error("Error sub index creator, dictionary index,save to files, Known Exception: {0}", ex.Message);
                throw;
            }
        }

        #endregion


        #region Private Methods

        /// <summary>
        /// Update one dictinoary entity
        /// </summary>
        /// <param name="term"></param>
        /// <param name="indexEntity"></param>
        private void UpdateDictionaryEntityInfo(string term, DictionaryIndexEntity indexEntity)
        {
            Dictionary<string, int> stopWord = ConfigProvider.StopWord.Get();

            indexEntity.IsStopped = stopWord.ContainsKey(term);
            indexEntity.DF = indexEntity.InvertedList.Count;

            Dictionary<PanGu.POS, int> posDic = new Dictionary<POS, int>();
            foreach (InvertedListEntity invertedList in indexEntity.InvertedList.Values)
            {
                indexEntity.TotalTF += invertedList.TFInDoc;

                foreach (HitListEntity hit in invertedList.HitList)
                {
                    if (hit.HTMLPosition == TermHTMLPosition.Title)
                        indexEntity.TotalTFInTitle++;

                    CommonUtility.Utility.AddItemCount<PanGu.POS>(posDic, hit.TermPos);
                }
            }

            if (posDic.Count >= 1)
            {
                indexEntity.TermPos = posDic.OrderByDescending(pair => pair.Value).
                    Select(pair => pair.Key).
                    Take(1).ToList()[0];
            }
        }

        #endregion


        #region Add Items to TermList

        private void AddSentence(int docID, string sentence, short senIndex, TermHTMLPosition termPos)
        {
            if (String.IsNullOrEmpty(sentence) || sentence.Length == 0)
            {
                return;
            }

            try
            {
                WordInfo[] wordList = segment.DoSegment(sentence).ToArray();
                int termNum = wordList.Length;
                for (short i = 0; i < termNum; i++)
                {
                    if (wordList[i].WordType == WordType.Symbol)
                        continue;
                    DictionaryIndexEntity dicEntity = AddTermToDictionary(wordList[i].Word);
                    dicEntity.TermType = wordList[i].WordType;
                    InvertedListEntity invertedList = AddTermToInvertedList(dicEntity.InvertedList, docID);
                    invertedList.AddHit(senIndex, i, wordList[i].Pos, termPos, 0);
                }
            }
            catch (Exception ex)
            {
                Logger logger = LogManager.GetCurrentClassLogger();
                logger.Error("In subindex creator, dictionary index, AddSentence: {0}", ex.Message);
                throw;
            }
        }

        private DictionaryIndexEntity AddTermToDictionary(string term)
        {
            if (!termList.ContainsKey(term))
            {
                termList.Add(term, new DictionaryIndexEntity(termList.Count));
            }
            return termList[term];
        }

        private static InvertedListEntity AddTermToInvertedList(Dictionary<int, InvertedListEntity> invertedList, int docID)
        {
            if (!invertedList.ContainsKey(docID))
            {
                invertedList.Add(docID, new InvertedListEntity(docID));
            }
            return invertedList[docID];
        }

        #endregion

        
        #region Private Members
        
        private Dictionary<string, DictionaryIndexEntity> termList;
        private Segment segment;

        #endregion
    }
}
